package com.lj.infoisland.service.Impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.lj.infoisland.common.convention.result.Result;
import com.lj.infoisland.common.convention.result.Results;
import com.lj.infoisland.domain.dto.NewsDTO;
import com.lj.infoisland.domain.es.EsNews;
import com.lj.infoisland.domain.po.News;
import com.lj.infoisland.domain.po.UserFavorites;
import com.lj.infoisland.domain.po.UserLikes;
import com.lj.infoisland.domain.po.Users;
import com.lj.infoisland.domain.vo.HotNewsVO;
import com.lj.infoisland.domain.vo.NewsVO;
import com.lj.infoisland.domain.vo.UserInfoVO;
import com.lj.infoisland.mapper.NewsMapper;
import com.lj.infoisland.service.*;
import com.lj.infoisland.util.UniqueRandomStringGenerator;
import com.lj.infoisland.util.UserContext;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.lj.infoisland.common.convention.errorcode.BaseErrorCode.USER_LOGIN_ERROR;
import static com.lj.infoisland.util.JwtUtil.extractUserIdFromToken;

/*
 * @description 新闻服务类实现类
 * @author 龙洁
 * @create 2024-10-21 下午10:40
 */
@Service
@RequiredArgsConstructor
public class NewsServiceImpl extends ServiceImpl<NewsMapper, News> implements NewsService {

    public static final String HOT_KEY = "news_hot_info_island_";
    private final NewsCategoriesServiceImpl newsCategoriesService;
    private final UserLikesService userLikesService;
    private final UserFavoritesService userFavoritesService;
    private final StringRedisTemplate redisTemplate;
    private final NewsDocumentOperator newsDocumentOperator;
    @Value("${minio.FILE_PREFIX}")
    private String FILE_PREFIX;
    @Lazy
    @Autowired
    private UsersService usersService;

    @Override
    public IPage<NewsVO> getUserNewsList(Page<News> page) {
        UserInfoVO user = UserContext.getUser();
        LambdaQueryWrapper<News> wrapper = Wrappers.lambdaQuery(News.class)
                .eq(News::getUserUniqueId, user.getUserUniqueId())
                .orderByDesc(News::getPublishedAt);
        Page<News> news = baseMapper.selectPage(page, wrapper);
        return news.convert(item -> {
            NewsVO bean = BeanUtil.toBean(item, NewsVO.class);
            bean.setCoverImage(FILE_PREFIX + item.getCoverImage());
            bean.setCategoryName(newsCategoriesService.getById(item.getCategoryId()).getName());
            bean.setPublished(item.getIsPublished() == 1);
            return bean;
        });
    }

    @Override
    public IPage<NewsVO> getNewsList(HttpServletRequest request, Page<News> newsPage, String categoryId) {
        // 从请求头中获取token
        String token = request.getHeader("Token");

        UserInfoVO user = null;

        if (StringUtils.hasLength(token)) {
            // 提取token中的userId
            String userUniqueId = extractUserIdFromToken(token);

            Result<UserInfoVO> userInfo = usersService.getUserInfo(userUniqueId);

            user = userInfo.getData();
        }

        LambdaQueryWrapper<News> wrapper = Wrappers.lambdaQuery(News.class)
                .orderByDesc(News::getPublishedAt);
        if (user != null && !user.getRole().equals("admin")) {
            wrapper.eq(News::getIsPublished, 1);
        }

        if (!categoryId.equals("0")) {
            wrapper.eq(News::getCategoryId, categoryId);
        }
        Page<News> news = baseMapper.selectPage(newsPage, wrapper);
        IPage<NewsVO> convert = news.convert(item -> {
            NewsVO bean = BeanUtil.toBean(item, NewsVO.class);
            bean.setCoverImage(FILE_PREFIX + item.getCoverImage());
            bean.setCategoryName(newsCategoriesService.getById(item.getCategoryId()).getName());
            return bean;
        });
        return convert;
    }

    @Override
    @Transactional
    public Result<?> likeNews(String newsId) {
        UserInfoVO user = UserContext.getUser();
        if (user == null) {
            return Results.failure(USER_LOGIN_ERROR);
        }
        LambdaQueryWrapper<UserLikes> eq = Wrappers.lambdaQuery(UserLikes.class)
                .eq(UserLikes::getNewsId, newsId)
                .eq(UserLikes::getUserUniqueId, user.getUserUniqueId());
        UserLikes one = userLikesService.getOne(eq);
        News news = baseMapper.selectOne(Wrappers.lambdaQuery(News.class).eq(News::getId, newsId));
        if (one == null) {
            UserLikes userLikes = new UserLikes();
            userLikes.setNewsId(newsId);
            userLikes.setUserUniqueId(user.getUserUniqueId());
            userLikesService.save(userLikes);
            usersService.update(Wrappers.lambdaUpdate(Users.class).eq(Users::getUserUniqueId, user.getUserUniqueId()).set(Users::getLikeNum, user.getLikeNum() + 1));
            news.setLikeNum(news.getLikeNum() + 1);
            baseMapper.updateById(news);
            // 获取当前日期
            LocalDate today = LocalDate.now();
            // 获取前一天的日期
            LocalDate yesterday = today.minusDays(1);
            // 创建一个DateTimeFormatter对象，用于格式化日期
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            // 格式化今天的日期
            String formattedTodayKey = HOT_KEY + today.format(formatter);
            // 增加热点值
            redisTemplate.opsForZSet().incrementScore(formattedTodayKey, newsId, 5);
        } else {
            userLikesService.remove(eq);
            usersService.update(Wrappers.lambdaUpdate(Users.class).eq(Users::getUserUniqueId, user.getUserUniqueId()).set(Users::getLikeNum, user.getLikeNum() - 1));
            news.setLikeNum(news.getLikeNum() - 1);
            baseMapper.updateById(news);
        }
        return Results.success();
    }

    @Override
    @Transactional
    public Result<?> collectNews(String newsId) {
        UserInfoVO user = UserContext.getUser();
        if (user == null) {
            return Results.failure(USER_LOGIN_ERROR);
        }
        LambdaQueryWrapper<UserFavorites> eq = Wrappers.lambdaQuery(UserFavorites.class)
                .eq(UserFavorites::getNewsId, newsId)
                .eq(UserFavorites::getUserUniqueId, user.getUserUniqueId());
        UserFavorites one = userFavoritesService.getOne(eq);
        News news = baseMapper.selectOne(Wrappers.lambdaQuery(News.class).eq(News::getId, newsId));
        if (one == null) {
            UserFavorites userFavorites = new UserFavorites();
            userFavorites.setNewsId(newsId);
            userFavorites.setUserUniqueId(user.getUserUniqueId());
            userFavoritesService.save(userFavorites);
            usersService.update(Wrappers.lambdaUpdate(Users.class).eq(Users::getUserUniqueId, user.getUserUniqueId()).set(Users::getFavoriteNum, user.getFavoriteNum() + 1));
            news.setFavoriteNum(news.getFavoriteNum() + 1);
            baseMapper.updateById(news);
            // 获取当前日期
            LocalDate today = LocalDate.now();
            // 获取前一天的日期
            LocalDate yesterday = today.minusDays(1);
            // 创建一个DateTimeFormatter对象，用于格式化日期
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            // 格式化今天的日期
            String formattedTodayKey = HOT_KEY + today.format(formatter);
            // 增加热点值
            redisTemplate.opsForZSet().incrementScore(formattedTodayKey, newsId, 10);
        } else {
            userFavoritesService.remove(eq);
            usersService.update(Wrappers.lambdaUpdate(Users.class).eq(Users::getUserUniqueId, user.getUserUniqueId()).set(Users::getFavoriteNum, user.getFavoriteNum() - 1));
            news.setFavoriteNum(news.getFavoriteNum() - 1);
            baseMapper.updateById(news);
        }
        return Results.success();
    }

    @Override
    public boolean isLiked(String newId) {
        UserInfoVO user = UserContext.getUser();
        if (user == null) {
            return false;
        }
        LambdaQueryWrapper<UserLikes> eq = Wrappers.lambdaQuery(UserLikes.class)
                .eq(UserLikes::getNewsId, newId)
                .eq(UserLikes::getUserUniqueId, user.getUserUniqueId());
        UserLikes one = userLikesService.getOne(eq);
        return one != null;
    }

    @Override
    public boolean isCollect(String newId) {
        UserInfoVO user = UserContext.getUser();
        if (user == null) {
            return false;
        }
        LambdaQueryWrapper<UserFavorites> eq = Wrappers.lambdaQuery(UserFavorites.class)
                .eq(UserFavorites::getNewsId, newId)
                .eq(UserFavorites::getUserUniqueId, user.getUserUniqueId());
        UserFavorites one = userFavoritesService.getOne(eq);
        return one != null;
    }

    @Override
    public IPage<NewsVO> searchNews(String keyword, Integer pageNum, Integer pageSize) {
        Page<News> page = new Page<>(pageNum, pageSize);
        // 在文章标题和描述中包含搜索关键词
        LambdaQueryWrapper<News> like = Wrappers.lambdaQuery(News.class)
                .like(News::getTitle, keyword)
                .or()
                .like(News::getDescription, keyword);
        Page<News> news = baseMapper.selectPage(page, like);
        return news.convert(item -> {
            NewsVO bean = BeanUtil.toBean(item, NewsVO.class);
            bean.setCoverImage(FILE_PREFIX + item.getCoverImage());
            bean.setCategoryName(newsCategoriesService.getById(item.getCategoryId()).getName());
            bean.setUsername(usersService.getUserNameById(item.getUserUniqueId()));
            return bean;
        });
    }

    @Override
    public Result<List<NewsVO>> getCollectNewsList() {
        UserInfoVO user = UserContext.getUser();
        List<String> list = userFavoritesService.list(Wrappers.lambdaQuery(UserFavorites.class)
                .eq(UserFavorites::getUserUniqueId, user.getUserUniqueId())).stream().map(UserFavorites::getNewsId).toList();
        if (CollectionUtil.isNotEmpty(list)) {
            List<News> newsList = baseMapper.selectBatchIds(list);
            return Results.success(newsList.stream().map(item -> {
                NewsVO bean = BeanUtil.toBean(item, NewsVO.class);
                bean.setCategoryName(newsCategoriesService.getById(item.getCategoryId()).getName());
                bean.setUsername(usersService.getUserNameById(item.getUserUniqueId()));
                bean.setCoverImage(FILE_PREFIX + item.getCoverImage());
                return bean;
            }).collect(Collectors.toList()));
        } else {
            return Results.success(null);
        }
    }

    @Override
    public Result<NewsVO> getHotNew(Integer categoryId) {
        News news = baseMapper.selectOne(Wrappers.lambdaQuery(News.class).eq(News::getCategoryId, categoryId).orderByDesc(News::getLikeNum).last("LIMIT 1"));
        if (news == null) {
            news = baseMapper.selectOne(Wrappers.lambdaQuery(News.class).orderByDesc(News::getLikeNum).last("LIMIT 1"));
        }
        NewsVO bean = BeanUtil.toBean(news, NewsVO.class);
        bean.setCoverImage(FILE_PREFIX + news.getCoverImage());
        bean.setCategoryName(newsCategoriesService.getById(news.getCategoryId()).getName());
        bean.setUsername(usersService.getUserNameById(news.getUserUniqueId()));
        return Results.success(bean);
    }

    @Override
    @Transactional
    public void addViewNum(String newId) {
        UserInfoVO user = UserContext.getUser();
        if (user == null) {
            return;
        }
        String key = "news_view_" + newId + "_" + user.getUserUniqueId();
        // 设置六小时才可以加一个访问记录
        Boolean b = redisTemplate.hasKey(key);
        if (Boolean.TRUE.equals(b)) {
            return;
        }
        LambdaQueryWrapper<News> eq = Wrappers.lambdaQuery(News.class).eq(News::getId, newId);
        News news = baseMapper.selectOne(eq);
        LambdaUpdateWrapper<News> set = Wrappers.lambdaUpdate(News.class).eq(News::getId, newId).set(News::getViewNum, news.getViewNum() + 1);
        baseMapper.update(news, set);
        redisTemplate.opsForValue().set(key, "1", 60 * 60 * 6, TimeUnit.SECONDS);
        // 获取当前日期
        LocalDate today = LocalDate.now();
        // 获取前一天的日期
        LocalDate yesterday = today.minusDays(1);
        // 创建一个DateTimeFormatter对象，用于格式化日期
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 格式化今天的日期
        String formattedTodayKey = HOT_KEY + today.format(formatter);
        // 增加热点值
        redisTemplate.opsForZSet().incrementScore(formattedTodayKey, newId, 1);
    }

    @Override
    public Result<List<HotNewsVO>> getHotNewsList() {
        // 获取当前日期
        LocalDate today = LocalDate.now();
        // 创建一个DateTimeFormatter对象，用于格式化日期
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        // 格式化今天的日期
        String formattedTodayKey = HOT_KEY + today.format(formatter);
        Set<String> top20Articles = redisTemplate.opsForZSet().reverseRange(formattedTodayKey, 0, 19);
        if (top20Articles != null) {
            List<HotNewsVO> list = top20Articles.stream().map(item -> {
                News news = baseMapper.selectById(item);
                return BeanUtil.toBean(news, HotNewsVO.class);
            }).toList();
            return Results.success(list);
        }
        return Results.success(null);
    }

    @Override
    @Transactional()
    public void publishNews(NewsDTO newsDTO, String contentUrl) {
        String id = UniqueRandomStringGenerator.generateUniqueString();
        News news = News.builder()
                .id(id)
                .title(newsDTO.getTitle())
                .description(newsDTO.getDescription())
                .categoryId(newsDTO.getCategoryId())
                .contentUrl(contentUrl)
                .coverImage(newsDTO.getCoverImage())
                .isPublished(1)
                .userUniqueId(UserContext.getUser().getUserUniqueId())
                .publishedAt(new Date())
                .build();
        EsNews esNews = new EsNews();
        BeanUtil.copyProperties(news, esNews);
        baseMapper.insert(news);
        newsDocumentOperator.saveDocument(esNews);
    }

    @Override
    public Result<List<NewsVO>> getSimilarNews(String id) {
        if (StrUtil.isNotBlank(id)) {
            News news = baseMapper.selectById(id);
            List<EsNews> similarNews = newsDocumentOperator.searchDocumentsByName(news.getTitle(), news.getDescription());
            List<NewsVO> collect = similarNews.stream().map(item -> {
                NewsVO bean = BeanUtil.toBean(item, NewsVO.class);
                bean.setCoverImage(FILE_PREFIX + item.getCoverImage());
                bean.setCategoryName(newsCategoriesService.getById(item.getCategoryId()).getName());
                return bean;
            }).filter(item -> !item.getId().equals(id)).collect(Collectors.toList());
            return Results.success(collect);
        }
        return null;
    }

    @Override
    public Integer getNewsCategoryCount(Integer value) {
        LambdaQueryWrapper<News> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(News::getCategoryId, value);
        return baseMapper.selectList(wrapper).size();
    }

    @Override
    public void updateNewsStatus(String id, Boolean published) {
        LambdaQueryWrapper<News> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(News::getId, id);
        News news = baseMapper.selectOne(wrapper);
        if (published) {
            news.setIsPublished(1);
        } else {
            news.setIsPublished(0);
        }
        updateById(news);
    }
}
